%{
  import java.io.*;
%}
      
%token SC                       /* semicolon  */
%token <sval> space             /* a space to dividing the different tokens */
%token <ival> Integer           /* an integer number */
%token <dval> Double            /* a double number */
%token <ival> Bool              /* a boolean value */
%token <sval> string            /* a string value */
%token <sval> Definition        /* a string for Definition */
%token <sval> Id                /* an identifier */
%token <sval> typeInt           /* integer datatype */
%token <sval> typeDouble        /* double datatype */
%token <sval> typeBool          /* boolean datatype */
%token <sval> Write             /* a token to print the value of a number or of a text */
%token <sval> WriteLine         /* a token to print the value of a number or of a text and go to newline */
%token <sval> If                /* a token for checking if a boolean statement is true */
%token <sval> Then              /* a token for executing commands if the case was true */
%token <sval> Else              /* a token for executing commands if the case was false */
%token <sval> Endif             /* a token to conclude the case statement */
%token <sval> While             /* a token for starting a loop */
%token <sval> Do                /* a token for executing commands if the boolean expression was true */
%token <sval> Endwhile          /* a token to conclude the loop */

%token <sval> Plus
%token <sval> Minus
%token <sval> Multiply
%token <sval> Divide
%token <sval> Exp
%token <sval> OpenPar           /*Open Parenthesis*/
%token <sval> ClosePar          /*Close Parenthesis*/
%token <sval> And
%token <sval> Or
%token <sval> Equal
%token <sval> Not
%token <sval> LessThan
%token <sval> LessEqual
%token <sval> Greater
%token <sval> GreaterEqual

%left Minus Plus
%left Multiply Divide 
%left Neg                       /* negation of unary minus */
%right Exp                      /* Exponential*/

%left Equal
%left Or
%left And
%left Not

%left LessThan LessEqual Greater GreaterEqual

%start program  /* start symbol */

%type <dval> math_exp
%type <ival> boolean_exp
%type <ival> math_comparison
      
%%

program:
        { manager = new Manager(); manager.createNewSymbolTable("main"); } scope { manager.backtrack(); manager.createOutput(); }                       
        ;

scope:
        | line scope    
        ;
      
line:     SC                            {}
        | math_exp SC                   {}
        | boolean_exp SC                {}
        | integer_definition SC         {}
        | double_definition SC          {}
        | boolean_definition SC         {}
        | assignment SC                 {}
        | write_text SC                 {}
        | writeLine_text SC             {}
        | if_then_statement SC          {}
        | while_do_statement SC         {}
        ;

integer_definition:
        /*Definition of a new variable with a value of an already defined one*/
        Definition space typeInt space Id space Id      { SymbolTableEntry temp1 = manager.lookup($5, false);
                                                                          if(temp1 == null) {
                                                                                  SymbolTableEntry temp2 = manager.lookup($7, true);
                                                                                  if(temp2 != null) {
                                                                                        //Type integer
                                                                                        if(temp2.getType() == 0)
                                                                                                manager.addEntry($5, temp2.getValueInteger());
                                                                                        //Type double
                                                                                        if(temp2.getType() == 1)
                                                                                                manager.addEntry($5, temp2.getValueInteger());
                                                                                        //Type boolean
                                                                                        if(temp2.getType() == 2)
                                                                                                yyerror("Variable exists but type doesn't match");
                                                                                  } else
                                                                                        yyerror("Variable to be assigned is not defined");
                                                                          } else
                                                                                yyerror("New variable already defined");
                                                                        }
        /*Definition of a new variable with a value*/                                                           
        | Definition space typeInt space Id space math_exp      { if(manager.lookup($5, false) == null) {
                                                                                manager.addEntry($5, (int)$7);
                                                                          } else {
                                                                                yyerror("New variable already defined");
                                                                          }
                                                                        }
        ;

double_definition:
        /*Definition of a new variable with a value of an already defined one*/
        Definition space typeDouble space Id space Id   { SymbolTableEntry temp1 = manager.lookup($5, false);
                                                                          if(temp1 == null) {
                                                                                  SymbolTableEntry temp2 = manager.lookup($7, true);
                                                                                  if(temp2 != null) {
                                                                                        if(temp2.getType() == 0)
                                                                                                manager.addEntry($5, temp2.getValueDouble());
                                                                                        if(temp2.getType() == 1)
                                                                                                manager.addEntry($5, temp2.getValueDouble());
                                                                                        if(temp2.getType() == 2)
                                                                                                yyerror("Variable exists but type doesn't match");
                                                                                  } else
                                                                                        yyerror("Variable to be assigned is not defined");
                                                                          } else
                                                                                yyerror("New variable already defined");
                                                                        }
        /*Definition of a new variable with a value*/                                                                    
        | Definition space typeDouble space Id space math_exp   { if(manager.lookup($5, false) == null)
                                                                        manager.addEntry($5, $7);
                                                                  else
                                                                        yyerror("New variable already defined");
                                                                }
        ;

boolean_definition:
        /*Definition of a new variable with a value of an already defined one*/
        Definition space typeBool space Id space Id     { SymbolTableEntry temp1 = manager.lookup($5, false);
                                                                          if(temp1 == null) {
                                                                                  SymbolTableEntry temp2 = manager.lookup($7, true);
                                                                                  if(temp2 != null) {
                                                                                        if(temp2.getType() == 0)
                                                                                                yyerror("Variable exists but type doesn't match");
                                                                                        if(temp2.getType() == 1)
                                                                                                yyerror("Variable exists but type doesn't match");
                                                                                        if(temp2.getType() == 2)
                                                                                                manager.addEntry($5, temp2.getValueBoolean());
                                                                                  } else
                                                                                        yyerror("Variable to be assigned is not defined");
                                                                          } else
                                                                                yyerror("New variable already defined");
                                                                        }
        /*Definition of a new variable with a value*/                                                                   
        | Definition space typeBool space Id space boolean_exp  { if(manager.lookup($5, false) == null)
                                                                                manager.addEntry($5, $7 == 1 ? true : false);
                                                                          else
                                                                                yyerror("New variable already defined");
                                                                        }
                ;

assignment:
        /*Assignment of a variable's value to another one*/
        Id space Id     { SymbolTableEntry temp1 = manager.lookup($1, true);
                                          if(temp1 != null) {
                                                  SymbolTableEntry temp2 = manager.lookup($3, true);
                                                  if(temp2 != null) {
                                                        if((temp1.getType() == temp2.getType()) || (temp1.getType() == 0 && temp2.getType() == 1) || 
                                                                (temp1.getType() == 1 && temp2.getType() == 0)) {
                                                                if(temp2.getType() == 0)
                                                                        manager.addEntry($1, temp2.getValueInteger());
                                                                if(temp2.getType() == 1)
                                                                        manager.addEntry($1, temp2.getValueDouble());
                                                                if(temp2.getType() == 2)
                                                                        manager.addEntry($1, temp2.getValueBoolean());
                                                        } else
                                                                yyerror("Variable exists but type doesn't match");
                                                  } else
                                                        yyerror("Variable to be assigned not defined yet");
                                          } else
                                                yyerror("Variable not defined");
                                        }
        /*Assignment of a value to a variable (int,double)*/                            
        | Id space math_exp     { SymbolTableEntry temp = manager.lookup($1, true);
                                          if(temp != null) {
                                                if(temp.getType() == 0)
                                                        temp.setValue((int)$3);
                                                else
                                                        if(temp.getType() == 1)
                                                                temp.setValue($3);
                                                        else
                                                                yyerror("Variable exists but type doesn't match");
                                          } else
                                                yyerror("Variable not defined");
                                        }
        /*Assignment of a value to a variable (bool)*/                                          
        | Id space boolean_exp  { SymbolTableEntry temp = manager.lookup($1, true);
                                          if(temp != null) {
                                                if(temp.getType() == 2)
                                                        temp.setValue($3 == 1 ? true : false);
                                                else
                                                        yyerror("Variable exists but type doesn't match");
                                          } else
                                                yyerror("Variable not defined"); 
                                        }
        ;

        
write_text:
          Write space string            { System.out.print($3); }
        | Write space math_exp  { System.out.print($3); }
        | Write space boolean_exp       { System.out.print($3 == 1 ? "true" : "false"); }
        | Write space Id        {
                                        SymbolTableEntry temp = manager.lookup($3, true);
                                        if(temp != null) {
                                                if(temp.getType() == 0)
                                                        System.out.print(temp.getValueInteger());
                                                if(temp.getType() == 1)
                                                        System.out.print(temp.getValueDouble());
                                                if(temp.getType() == 2)
                                                        System.out.print(temp.getValueBoolean());
                                        } else
                                                yyerror("Variable not defined"); 
                                }
        ;

writeLine_text:
          WriteLine space string        { System.out.println($3); }
        | WriteLine space math_exp      { System.out.println($3); }
        | WriteLine space boolean_exp   { System.out.println($3 == 1 ? "true" : "false"); }
        | WriteLine space Id    {
                                        SymbolTableEntry temp = manager.lookup($3, true);
                                        if(temp != null) {
                                                if(temp.getType() == 0)
                                                        System.out.println(temp.getValueInteger());
                                                if(temp.getType() == 1)
                                                        System.out.println(temp.getValueDouble());
                                                if(temp.getType() == 2)
                                                        System.out.println(String.valueOf(temp.getValueBoolean()));
                                        } else
                                                yyerror("Variable not defined"); 
                                }
        ;
        

/* mathematical expression */
math_exp:
          Integer                               { $$ = $1; }
        | Double                                { $$ = $1; }
        | math_exp Plus math_exp                { $$ = $1 + $3; }
        | math_exp Minus math_exp               { $$ = $1 - $3; }
        | math_exp Multiply math_exp            { $$ = $1 * $3; }
        | math_exp Divide math_exp              { $$ = $1 / $3; }
        | Minus math_exp  %prec Neg             { $$ = -$2; }
        | math_exp Exp math_exp                 { $$ = Math.pow($1, $3); }
        | OpenPar math_exp ClosePar             { $$ = $2; }
        ;

/* boolean expression */
boolean_exp:
          Bool                                  { $$ = $1; }
        | math_comparison                       { $$ = $1; }
        | boolean_exp And boolean_exp           { $$ = (($1 == 1 ? true : false) && ($3 == 1 ? true : false)) ? 1 : 0;}
        | boolean_exp Or boolean_exp            { $$ = (($1 == 1 ? true : false) || ($3 == 1 ? true : false)) ? 1 : 0; }
        | boolean_exp Equal boolean_exp         { $$ = (($1 == 1 ? true : false) == ($3 == 1 ? true : false)) ? 1 : 0; }
        | Not boolean_exp                       { $$ = $2 == 1 ? 0 : 1; }
        | OpenPar boolean_exp ClosePar          { $$ = $2; }
        ;

/* Comparison beetween mathematical expressions, returning the boolean result */
math_comparison:
          math_exp Equal math_exp               { $$ = ($1 == $3) ? 1 : 0; }
        | math_exp LessThan math_exp            { $$ = ($1 < $3) ? 1 : 0; }
        | math_exp LessEqual math_exp           { $$ = ($1 <= $3) ? 1 : 0; }
        | math_exp Greater math_exp             { $$ = ($1 > $3) ? 1 : 0; }
        | math_exp GreaterEqual math_exp        { $$ = ($1 >= $3) ? 1 : 0; }
        ;

if_then_statement:
        If space boolean_exp space Then SC {manager.createNewSymbolTable("If");} scope if_then_else_statement   
        ;

if_then_else_statement:
          Endif {manager.backtrack();}
        | Else SC {manager.backtrack();manager.createNewSymbolTable("Else");} scope Endif {manager.backtrack();}
        ;

while_do_statement:
        While space boolean_exp space Do SC {manager.createNewSymbolTable("While");} scope Endwhile     {manager.backtrack();}
        ;

%%
  private Yylex lexer;
  static Manager manager = null;
  static boolean created = false;

  private int yylex () {
    int yyl_return = -1;
    try {
      yylval = new ParserVal(0);
      yyl_return = lexer.yylex();
    }
    catch (IOException e) {
      System.err.println("IO error :"+e);
    }
    return yyl_return;
  }


  public void yyerror (String error) {
    System.err.println ("Error: " + error);
  }


  public Parser(Reader r) {
    lexer = new Yylex(r, this);
  }


  public static void main(String args[]) throws IOException {

    Parser yyparser;
    if ( args.length > 0 ) 
    {
      System.out.println();
      System.out.println("Parsing started...");
      System.out.println();
      // parse the input file
      yyparser = new Parser(new FileReader(args[0]));
    }
    else 
    {
      System.out.println("Parser started...");
      System.out.println("[To exit press CTRL-C]");
      System.out.println("Input your commands...");
            yyparser = new Parser(new InputStreamReader(System.in));
    }

    yyparser.yyparse();
    
    System.out.println();
    System.out.println("Parsing done!");
    System.out.println();
    
  }

